package wsz.mapper;

import lombok.Data;
import wsz.pojo.Configuration;
import wsz.pojo.MappedStatement;
import wsz.sqlSession.SqlSession;
import wsz.utils.TypeParameterResolver;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Map;

/**
 * mapper.xml文件中statementId的真正执行
 * @author wsz
 * @date 2021/10/9 10:10
 **/
public class MapperMethod {

    private SqlCommand command;
    private MethodSignature method;


    public <T> MapperMethod(Class<T> mapperInterface, Method method, Configuration configuration) {
        this.command = new MapperMethod.SqlCommand(configuration, mapperInterface, method);
        this.method = new MapperMethod.MethodSignature(mapperInterface, method);
    }

    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        switch (this.command.getType()) {
            case INSERT: {
                result = rowCountResult(sqlSession.insert(command.getName(), args));
                break;
            }
            case UPDATE: {
                result = rowCountResult(sqlSession.update(command.getName(), args));
                break;
            }
            case DELETE: {
                result = rowCountResult(sqlSession.delete(command.getName(), args));
                break;
            }
            case SELECT:
                // TODO 只处理list/map/object类型
                if (method.isReturnsMany()) {
                    result = executeForMany(sqlSession, args);
                } else if (method.isReturnsMap()) {
                    result = executeForMap(sqlSession, args);
                } else {
                    result = sqlSession.selectOne(command.getName(), args);
                }
                break;
            default:
                throw new RuntimeException("type not found");
        }
        if (result == null) {
            throw new RuntimeException("execute return null");
        }
        return result;
    }

    /**
     * 处理数值返回结果
     * @param rowCount
     * @return
     */
    private Object rowCountResult(int rowCount) {
        final Object result;
        if (method.isReturnsVoid()) {
            result = null;
        } else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
            result = rowCount;
        } else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
            result = (long)rowCount;
        } else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
            result = rowCount > 0;
        } else {
            throw new RuntimeException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
        }
        return result;
    }

    /**
     * list返回
     * @param sqlSession
     * @param args
     * @param <E>
     * @return
     */
    private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
        return sqlSession.selectList(this.command.getName(), args);
    }

    /**
     * map返回，暂不处理分页
     * @param sqlSession
     * @param args
     * @param <K>
     * @param <V>
     * @return
     */
    private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) {
        Map result = sqlSession.selectMap(this.command.getName(), args);
        return result;
    }

    /**
     * sql标识
     */
    @Data
    public static class SqlCommand {
        private final String name;
        private final SqlCommandType type;

        public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
            String methodName = method.getName();
            Class<?> declaringClass = method.getDeclaringClass();
            MappedStatement ms = this.resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration);
            if (ms == null) {
                throw new RuntimeException("Unknown found mapper");
            }

            // TODO name为statementId
            this.name = mapperInterface.getName() + "." + ms.getId();
            this.type = ms.getSqlCommandType();
            if (this.type == SqlCommandType.UNKNOWN) {
                throw new RuntimeException("Unknown execution method for: " + this.name);
            }

        }

        private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName, Class<?> declaringClass, Configuration configuration) {
            String statementId = mapperInterface.getName() + "." + methodName;
            if (configuration.hasStatement(statementId)) {
                return configuration.getMappedStatement(statementId);
            } else if (mapperInterface.equals(declaringClass)) {
                return null;
            } else {
                Class[] var6 = mapperInterface.getInterfaces();
                int var7 = var6.length;

                for(int var8 = 0; var8 < var7; ++var8) {
                    Class<?> superInterface = var6[var8];
                    if (declaringClass.isAssignableFrom(superInterface)) {
                        MappedStatement ms = this.resolveMappedStatement(superInterface, methodName, declaringClass, configuration);
                        if (ms != null) {
                            return ms;
                        }
                    }
                }

                return null;
            }
        }
    }

    /**
     * 封装接口调用的相关信息
     */
    @Data
    public static class MethodSignature {

        private final boolean returnsMany;
        private final boolean returnsMap;
        private final boolean returnsVoid;

        private final Class<?> returnType;
        private final String mapKey;

        public MethodSignature(Class mapperInterface, Method method) {
            Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
            if (resolvedReturnType instanceof Class<?>) {
                this.returnType = (Class<?>) resolvedReturnType;
            } else if (resolvedReturnType instanceof ParameterizedType) {
                this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
            } else {
                this.returnType = method.getReturnType();
            }
            this.returnsVoid = void.class.equals(this.returnType);
            this.returnsMany = Collection.class.isAssignableFrom(this.returnType) || this.returnType.isArray();

            this.mapKey = getMapKey(method);
            this.returnsMap = (this.mapKey != null);

        }

        private String getMapKey(Method method) {
            String mapKey = null;
            if (Map.class.isAssignableFrom(method.getReturnType())) {
                mapKey = "isMap";
            }
            return mapKey;
        }
    }
}
